# -*- coding: cp866 -*-
import sys
import base64
import time
import datetime
import logging
try:
    import cloghandler
except Exception, err:
    sys.stderr.write('WARNING: can\'t load "cloghandler": %s' % (str(err)))
try:
    import kinterbasdb; kinterbasdb.init(type_conv=200)
except Exception,err:
    sys.stderr.write('WARNING: can\'t load "kinterbasdb": %s' % (str(err)))
try:
    import pyodbc
    MSDBDriver = "pyodbc"
except:
    try:
        import pymssql
        MSDBDriver = "pymssql"
    except Exception,err:
        sys.stderr.write('WARNING: can\'t load noone of "pyodbc" and "pymssql": %s' % str(err))

import os
import urllib2
urllib = urllib2
import pdb
try:
    import multiprocessing
except Exception, err:
    sys.stderr.write('WARNING: can\'t load "multiprocessing": %s' % str(err))
import threading

g_dirsep = None
if os.name == 'posix':  g_dirsep = '/'
if os.name == 'nt':     g_dirsep = '\\'
import ConfigParser

CONFIG_FILE     = 'config.cfg'
CONFIG_RELOAD_TIMEOUT = int(180)
LOGGER_PREFIX   = 'main'
EVENT_WINDOW    = datetime.timedelta(seconds=30*60)

class MSConfig:
    sample =    {
                 'main':
                 {
                  'host':   '0.0.0.0',
                  'port':   '8081',
                  'heartbeat_port':'17030',
                  'root':  os.getcwd(),
                  'js':    os.getcwd() + g_dirsep + 'js' + g_dirsep,
                  'css':   os.getcwd() + g_dirsep + 'css' + g_dirsep,
                  'html':  os.getcwd() + g_dirsep + 'www-data' + g_dirsep,
                  'image': os.getcwd() + g_dirsep + 'images' + g_dirsep,
                  'dbpath':os.getcwd() + g_dirsep + 'db' + g_dirsep,
                  'logs':  os.getcwd() + g_dirsep + 'logs' + g_dirsep,
                  'loglevel': 'debug',
                 },
                 'msdb':
                 {
                  'host':   'localhost',
                  'port':   '1',
                  'dbname': 'noname',
                  'user':   'anonymous',
                  'pwd':    '',
                 },
                 'msapi':
                 {
                  'url':    'localhost',
                  'objects_cfg': 'api_objects.cfg',
                 },
                }
    ready = {}

    @staticmethod
    def load(path, name):
        global g_dirsep
        cfg = ConfigParser.RawConfigParser()
        if os.path.exists(path + g_dirsep + name): cfg.read(path + g_dirsep + name)
        for section, body in MSConfig.sample.iteritems():
            if not cfg.has_section(section): cfg.add_section(section)
            for opt, value in body.iteritems():
                if not cfg.has_option(section, opt): cfg.set(section, opt, value)

        if not os.path.exists(cfg.get('main', 'dbpath')):
            os.makedirs(cfg.get('main','dbpath'))
        if not os.path.exists(cfg.get('main', 'logs')):
            os.makedirs(cfg.get('main','logs'))
        return cfg

    @staticmethod
    def getConfig(name=CONFIG_FILE):
        ts = int(time.time())
        config = MSConfig.ready.get(name, None)
        if (config == None) or (ts - config[1] > CONFIG_RELOAD_TIMEOUT):
            config = MSConfig.load(os.getcwd(), name)
            config = (config, ts)
            MSConfig.ready[name] = config
            #TODO: dump config to temp file at reload
        return config[0]

log = None
logWriter = None
logReader = None
logObjects = None
logDatabase = None
class MSLogger:
    levels  =   {
                 'debug':   logging.DEBUG,
                 'info':    logging.INFO,
                 'error':   logging.ERROR,
                }
    loggers = {}

    @staticmethod
    def configure(name, configName):
        levelIn = MSConfig.getConfig(configName).get('main','loglevel')
        level = MSLogger.levels.get(levelIn, None)
        if not level: raise RuntimeError('MSLogger::configure: wrong log level specified: ' + str(levelIn))
        l = logging.getLogger(name)
        l.setLevel(level)

        try:
            f = cloghandler.ConcurrentRotatingFileHandler(MSConfig.getConfig(configName).get('main', 'logs') + g_dirsep + name + '.log', 'a', 1024*1024*5, 10)
        except:
            f = logging.FileHandler(MSConfig.getConfig(configName).get('main', 'logs') + g_dirsep + name + '.log')
            print 'WARNING: can\'t use cloghandler.ConcurrentRotatingFileHandler - using logging.FileHandler'
        f.setLevel(level)

        c = logging.StreamHandler()
        c.setLevel(level)

        fmt = logging.Formatter('%(asctime)s::%(name)s::%(levelname)s: %(message)s')
        f.setFormatter(fmt)
        c.setFormatter(fmt)
        l.addHandler(f)
        l.addHandler(c)
        return l

    @staticmethod
    def configureObjectsLogger(name, configName):
        levelIn = MSConfig.getConfig(configName).get('main','loglevel')
        level = MSLogger.levels.get(levelIn, None)
        if not level: raise RuntimeError('MSLogger::configure: wrong log level specified: ' + str(levelIn))
        l = logging.getLogger(name)
        l.setLevel(level)

        try:
            f = cloghandler.ConcurrentRotatingFileHandler(MSConfig.getConfig(configName).get('main', 'logs') + g_dirsep + name + '.log', 'a', 1024*1024*5, 10)
        except:
            f = logging.FileHandler(MSConfig.getConfig(configName).get('main', 'logs') + g_dirsep + name + '.log')
            print 'WARNING: can\'t use cloghandler.ConcurrentRotatingFileHandler - using logging.FileHandler'
        f.setLevel(level)

        fmt = logging.Formatter('%(asctime)s::%(name)s::%(levelname)s: %(message)s')
        f.setFormatter(fmt)
        l.addHandler(f)
        return l

    @staticmethod
    def create(name, createIfNeeded, configName):
        logger = MSLogger.loggers.get(name, None)
        if logger == None:
            if createIfNeeded == False: raise RuntimeError('MSLogger::get: %s not configured' % (str(name)))
            if name == 'ms.safeinside.objects':
                logger = MSLogger.configureObjectsLogger(name, configName)
            else:
                logger = MSLogger.configure(name, configName)
            MSLogger.loggers[name] = logger
        return logger

    @staticmethod
    def get(name, configName=CONFIG_FILE): return MSLogger.create(name, False, configName)

    @staticmethod
    def init(configName):
        global log
        global logWriter
        global logReader
        global logObjects
        global logDatabase
        log         = MSLogger.create('ms.main', True, configName)
        logWriter   = MSLogger.create('ms.writer', True, configName)
        logReader   = MSLogger.create('ms.reader', True, configName)
        logObjects  = MSLogger.create('ms.objects', True, configName)
        logDatabase = MSLogger.create('ms.database', True, configName)


class MSDatetime:
    FormatMagicAPI  = '%d%m%Y_%H%M%S'     #time format descriptor used in magic api
    FormatMagicDB   = '%Y-%m-%d %H:%M:%S'
    FormatPCNDB     = '%Y-%m-%d %H:%M:%S'   #time format descriptor used in pcn db
    FormatCesar     = '%d.%m.%Y %H:%M:%S'

class MSObject:
    def __init__(self):
        self.objectName = ''
        self.objectSKey = ''
        self.objectMSId = 0
        self.objectPCNId= 0

    def __cmp__(x, y):
        if y == None: return 1
        return x.objectPCNId < y.objectPCNId

class MSObjects:
    @staticmethod
    def readObjectsFromMSDB(dbCursor, objects, prefix):
        logObjects.debug('readObjectsFromMSDB: started')
        query = 'SELECT id,uid,lastEventTime,lastEventType,versionSoftPGSM FROM _devices'
        dbCursor.execute(query)
        uids  = dbCursor.fetchall()
        for uid in uids:
            try:
                query = 'SELECT login,password FROM _users WHERE defaultDevice=%s' % str(uid[0])
                dbCursor.execute(query)
                deviceData = dbCursor.fetchall()
                devicePCNId = deviceData[0][0].replace(prefix,'')
                o = MSObject()
                o.objectMSId = int(uid[0])
                o.objectPCNId = int(devicePCNId)
                try:
                    o.lastEventTime = uid[2].strftime(MSDatetime.FormatMagicDB)
                    o.lastEventTime = uid[2]
                    o.lastEventType = int(uid[3])
                    o.versionSoftPGSM = str(uid[4])
                except Exception, err:
                    logObjects.warning('readObjectsFromMSDB: msid(%s) can\'t get params: %s' % (str(uid[0]), str(err)))
                    o.lastEventTime = datetime.datetime(2012, 1, 1)
                    o.lastEventType = 0
                    o.versionSoftPGSM = 'Undefined'
                objects[o.objectMSId] = o
                logObjects.debug('readObjectsFromMSDB: loaded msid(%d) pcnid(%d) prefix(%s)', int(uid[0]), int(devicePCNId), str(prefix))
            except Exception, err:
                logObjects.warning('readObjectsFromMSDB: msid(%s) error: %s' % (str(uid[0]), str(err)))
                continue
        logObjects.info('readObjectsFromMSDB: done %d objects', len(objects))

    @staticmethod
    def readObjectsFromFile(path, objects):
        logObjects.debug('readObjectsFromFile: started')
        def parseCfgLine(CfgLine):
            s = CfgLine.strip().split(' ')
            e = {}
            for prop in s[0:len(s)]:
                prop = prop.split('=')
                e[prop[0]] = prop[1]
            return e
        try:
            f = open(path, 'r')
            cdata = f.readlines()
        except:
            return
        for s in cdata:
            try:
                e = parseCfgLine(s)
                o = MSObject()
                o.objectMSId = 0
                o.objectPCNId = int(e['obj_id'])
                o.objectSKey = e['obj_skey']
                objects[o.objectPCNId] = o
                logObjects.debug('readObjectsFromFile: loaded msid(%d) pcnid(%d) skey(%s)', o.objectMSId, o.objectPCNId, o.objectSKey)
            except Exception, err:
                logObjects.warning('readObjectsFromFile: cfgline: %s', str(s))
                logObjects.warning('readObjectsFromFile: error: %s', str(err))
                continue
        logObjects.info('readObjectsFromFile: done %d objects', len(objects))

    @staticmethod
    def filterObjectsAs_NORMAL_TEST(objects, filterRulesFName):
        try:
            f = open(filterRulesFName, 'r')
        except:
            for k,o in objects.iteritems():
                o.isTestObject = True
            return (objects, {})
        ids_norm = []
        for msid in f.readlines():
            msid = msid.strip()
            if len(msid): ids_norm.append(msid)

        objs_test = {}
        objs_norm = {}
        for msid,o in objects.iteritems():
            if str(o.objectPCNId) in ids_norm:
                o.isTestObject = False
                objs_norm[msid] = o
            else:
                o.isTestObject = True
                objs_test[msid] = o
        return (objs_test, objs_norm)

    @staticmethod
    def makeObject_TEST(filterRulesFName, oid):
        try:
            f = open(filterRulesFName, 'r')
        except:
            return

        norm_ids = []
        for o in f.readlines():
            o = o.strip()
            if len(o): norm_ids.append(o)
        if oid in norm_ids: norm_ids.remove(oid)
        f = open(filterRulesFName, 'w')
        norm_ids.sort()
        for o in norm_ids:
            f.write('%s\n' % o)
        f.close()

    @staticmethod
    def makeObject_NORMAL(filterRulesFName, oid):
        norm_ids = []
        try:
            f = open(filterRulesFName, 'r')
            for o in f.readlines():
                o = o.strip()
                if len(o): norm_ids.append(o)
        except:
            pass

        if not (oid in norm_ids): norm_ids.append(oid)
        f = open(filterRulesFName, 'w')
        norm_ids.sort()
        for o in norm_ids:
            f.write('%s\n' % o)
        f.close()

    @staticmethod
    def registrateObjectInMSDB(connection, filterRulesFName, oid, mac):
        dbprefix = '[%s].[dbo]' % MSConfig.getConfig().get('msdb','dbname')
        cursor = connection.cursor()
        login   = 'dev' + str(oid)
        pwd     = 'pass' + str(oid)
        query   = "SELECT login,password FROM %s.[_users] WHERE login='%s'" % (dbprefix, login)
        logObjects.debug('registrateObjectInMSDB:%s: query1: %s', oid, query)
        cursor.execute(query)
        t = cursor.fetchall()
        if len(t) > 0 and t[0][0] == login: raise RuntimeError('device %s already exists!' % (login))
        MSObjects.makeObject_TEST(filterRulesFName, oid)

        query   = "INSERT INTO %s.[_devices] (uid) VALUES ('%s')" % (dbprefix, str(mac))
        logObjects.debug('registrateObjectInMSDB:%s: query2: %s', oid, query)
        cursor.execute(query)
        connection.commit()

        cursor  = connection.cursor()
        query   = "SELECT IDENT_CURRENT('%s.[_devices]')" % (dbprefix)
        logObjects.debug('registrateObjectInMSDB:%s: query3: %s', oid, query)
        cursor.execute(query)
        t = cursor.fetchall()
        msid = int(t[0][0])

        query   = "INSERT INTO %s.[_users] (login, password, defaultDevice) VALUES ('%s', '%s', '%s')" % (dbprefix, login, pwd, str(msid))
        logObjects.debug('registrateObjectInMSDB:%s: query4: %s', oid, query)
        cursor.execute(query)
        connection.commit()

        device_value = 'login=%s&password=%s&exportSupport=1' % (login, pwd)
        device_ctime = datetime.datetime.fromtimestamp(time.time()).strftime('%Y%m%d %H:%M:%S')
        query   = "INSERT INTO %s.[Security_values] (\"key\", \"value\", \"date\") VALUES ('%s', '%s', '%s')" % (dbprefix, mac, device_value, device_ctime)
        logObjects.debug('registrateObjectInMSDB:%s: query5: %s', oid, query)
        cursor = connection.cursor()
        cursor.execute(query)
        connection.commit()

        deviceId = str(msid)
        gps = "127,107,106,105,104,103,102,101,100,97,96,89,88,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,59,40,26"
        gsm = "127,108,107,106,105,104,103,102,101,100,97,96,89,88,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,59,48,47,46,45,40,26"
        balance = "127,120,110,108,107,106,105,104,103,102,101,100,98,97,96,95,94,93,92,91,89,88,87,86,85,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,40,39,35,34,33,32,31,30,29,28,27,26,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1"
        brk = "120,110,108,107,106,105,104,103,102,101,100,98,97,96,95,94,93,92,91,89,88,87,86,85,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,57,56,55,54,53,52,51,50,49,44,43,40,35,34,33,32,31,26,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1"
        cam1 = "127,127,127,127,127,127,127,127,126,126,126,126,126,126,126,126,125,125,125,125,125,125,125,125,124,124,124,124,124,124,124,124,123,123,123,123,123,123,123,123,122,122,122,122,122,122,122,122,121,121,121,121,121,121,121,121,120,120,120,120,120,120,120,120,115,115,115,115,115,115,115,115,114,114,114,114,114,114,114,114,113,113,113,113,113,113,113,113,112,112,112,112,112,112,112,112,111,111,111,111,111,111,111,111,110,110,110,110,110,110,110,110,108,108,108,108,108,108,108,108,107,107,107,107,107,107,107,107,106,106,106,106,106,106,106,106,105,105,105,105,105,105,105,105,104,104,104,104,104,104,104,104,103,103,103,103,103,103,103,103,102,102,102,102,102,102,102,102,101,101,101,101,101,101,101,101,100,100,100,100,100,100,100,100,99,99,99,99,99,99,99,99,98,98,98,98,98,98,98,98,97,97,97,97,97,97,97,97,96,96,96,96,96,96,96,96,95,95,95,95,95,95,95,95,94,94,94,94,94,94,94,94,93,93,93,93,93,93,93,93,92,92,92,92,92,92,92,92,91,91,91,91,91,91,91,91,90,90,90,90,90,90,90,90,88,88,88,88,88,88,88,88,"
        cam2 = "127,127,127,127,127,127,127,127,126,126,126,126,126,126,126,126,125,125,125,125,125,125,125,125,124,124,124,124,124,124,124,124,123,123,123,123,123,123,123,123,122,122,122,122,122,122,122,122,121,121,121,121,121,121,121,121,120,120,120,120,120,120,120,120,115,115,115,115,115,115,115,115,114,114,114,114,114,114,114,114,113,113,113,113,113,113,113,113,112,112,112,112,112,112,112,112,111,111,111,111,111,111,111,111,110,110,110,110,110,110,110,110,108,108,108,108,108,108,108,108,107,107,107,107,107,107,107,107,106,106,106,106,106,106,106,106,105,105,105,105,105,105,105,105,104,104,104,104,104,104,104,104,103,103,103,103,103,103,103,103,102,102,102,102,102,102,102,102,101,101,101,101,101,101,101,101,100,100,100,100,100,100,100,100,99,99,99,99,99,99,99,99,98,98,98,98,98,98,98,98,97,97,97,97,97,97,97,97,96,96,96,96,96,96,96,96,95,95,95,95,95,95,95,95,94,94,94,94,94,94,94,94,93,93,93,93,93,93,93,93,92,92,92,92,92,92,92,92,91,91,91,91,91,91,91,91,90,90,90,90,90,90,90,90,88,88,88,88,88,88,88,88,"
        cam3 = "127,127,127,127,127,127,127,127,126,126,126,126,126,126,126,126,125,125,125,125,125,125,125,125,124,124,124,124,124,124,124,124,123,123,123,123,123,123,123,123,122,122,122,122,122,122,122,122,121,121,121,121,121,121,121,121,120,120,120,120,120,120,120,120,115,115,115,115,115,115,115,115,114,114,114,114,114,114,114,114,113,113,113,113,113,113,113,113,112,112,112,112,112,112,112,112,111,111,111,111,111,111,111,111,110,110,110,110,110,110,110,110,108,108,108,108,108,108,108,108,107,107,107,107,107,107,107,107,106,106,106,106,106,106,106,106,105,105,105,105,105,105,105,105,104,104,104,104,104,104,104,104,103,103,103,103,103,103,103,103,102,102,102,102,102,102,102,102,101,101,101,101,101,101,101,101,100,100,100,100,100,100,100,100,99,99,99,99,99,99,99,99,98,98,98,98,98,98,98,98,97,97,97,97,97,97,97,97,96,96,96,96,96,96,96,96,95,95,95,95,95,95,95,95,94,94,94,94,94,94,94,94,93,93,93,93,93,93,93,93,92,92,92,92,92,92,92,92,91,91,91,91,91,91,91,91,90,90,90,90,90,90,90,90,88,88,88,88,88,88,88,88,"
        cam4 = "127,127,127,127,127,127,127,127,126,126,126,126,126,126,126,126,125,125,125,125,125,125,125,125,124,124,124,124,124,124,124,124,123,123,123,123,123,123,123,123,122,122,122,122,122,122,122,122,121,121,121,121,121,121,121,121,120,120,120,120,120,120,120,120,115,115,115,115,115,115,115,115,114,114,114,114,114,114,114,114,113,113,113,113,113,113,113,113,112,112,112,112,112,112,112,112,111,111,111,111,111,111,111,111,110,110,110,110,110,110,110,110,108,108,108,108,108,108,108,108,107,107,107,107,107,107,107,107,106,106,106,106,106,106,106,106,105,105,105,105,105,105,105,105,104,104,104,104,104,104,104,104,103,103,103,103,103,103,103,103,102,102,102,102,102,102,102,102,101,101,101,101,101,101,101,101,100,100,100,100,100,100,100,100,99,99,99,99,99,99,99,99,98,98,98,98,98,98,98,98,97,97,97,97,97,97,97,97,96,96,96,96,96,96,96,96,95,95,95,95,95,95,95,95,94,94,94,94,94,94,94,94,93,93,93,93,93,93,93,93,92,92,92,92,92,92,92,92,91,91,91,91,91,91,91,91,90,90,90,90,90,90,90,90,88,88,88,88,88,88,88,88,"
        cam5 = "127,127,127,127,127,127,127,127,126,126,126,126,126,126,126,126,125,125,125,125,125,125,125,125,124,124,124,124,124,124,124,124,123,123,123,123,123,123,123,123,122,122,122,122,122,122,122,122,121,121,121,121,121,121,121,121,120,120,120,120,120,120,120,120,115,115,115,115,115,115,115,115,114,114,114,114,114,114,114,114,113,113,113,113,113,113,113,113,112,112,112,112,112,112,112,112,111,111,111,111,111,111,111,111,110,110,110,110,110,110,110,110,108,108,108,108,108,108,108,108,107,107,107,107,107,107,107,107,106,106,106,106,106,106,106,106,105,105,105,105,105,105,105,105,104,104,104,104,104,104,104,104,103,103,103,103,103,103,103,103,102,102,102,102,102,102,102,102,101,101,101,101,101,101,101,101,100,100,100,100,100,100,100,100,99,99,99,99,99,99,99,99,98,98,98,98,98,98,98,98,97,97,97,97,97,97,97,97,96,96,96,96,96,96,96,96,95,95,95,95,95,95,95,95,94,94,94,94,94,94,94,94,93,93,93,93,93,93,93,93,92,92,92,92,92,92,92,92,91,91,91,91,91,91,91,91,90,90,90,90,90,90,90,90,88,88,88,88,88,88,88,88,"
        cam6 = "127,127,127,127,127,127,127,127,126,126,126,126,126,126,126,126,125,125,125,125,125,125,125,125,124,124,124,124,124,124,124,124,123,123,123,123,123,123,123,123,122,122,122,122,122,122,122,122,121,121,121,121,121,121,121,121,120,120,120,120,120,120,120,120,115,115,115,115,115,115,115,115,114,114,114,114,114,114,114,114,113,113,113,113,113,113,113,113,112,112,112,112,112,112,112,112,111,111,111,111,111,111,111,111,110,110,110,110,110,110,110,110,108,108,108,108,108,108,108,108,107,107,107,107,107,107,107,107,106,106,106,106,106,106,106,106,105,105,105,105,105,105,105,105,104,104,104,104,104,104,104,104,103,103,103,103,103,103,103,103,102,102,102,102,102,102,102,102,101,101,101,101,101,101,101,101,100,100,100,100,100,100,100,100,99,99,99,99,99,99,99,99,98,98,98,98,98,98,98,98,97,97,97,97,97,97,97,97,96,96,96,96,96,96,96,96,95,95,95,95,95,95,95,95,94,94,94,94,94,94,94,94,93,93,93,93,93,93,93,93,92,92,92,92,92,92,92,92,91,91,91,91,91,91,91,91,90,90,90,90,90,90,90,90,88,88,88,88,88,88,88,88,"
        cam7 = "127,127,127,127,127,127,127,127,126,126,126,126,126,126,126,126,125,125,125,125,125,125,125,125,124,124,124,124,124,124,124,124,123,123,123,123,123,123,123,123,122,122,122,122,122,122,122,122,121,121,121,121,121,121,121,121,120,120,120,120,120,120,120,120,115,115,115,115,115,115,115,115,114,114,114,114,114,114,114,114,113,113,113,113,113,113,113,113,112,112,112,112,112,112,112,112,111,111,111,111,111,111,111,111,110,110,110,110,110,110,110,110,108,108,108,108,108,108,108,108,107,107,107,107,107,107,107,107,106,106,106,106,106,106,106,106,105,105,105,105,105,105,105,105,104,104,104,104,104,104,104,104,103,103,103,103,103,103,103,103,102,102,102,102,102,102,102,102,101,101,101,101,101,101,101,101,100,100,100,100,100,100,100,100,99,99,99,99,99,99,99,99,98,98,98,98,98,98,98,98,97,97,97,97,97,97,97,97,96,96,96,96,96,96,96,96,95,95,95,95,95,95,95,95,94,94,94,94,94,94,94,94,93,93,93,93,93,93,93,93,92,92,92,92,92,92,92,92,91,91,91,91,91,91,91,91,90,90,90,90,90,90,90,90,88,88,88,88,88,88,88,88,"
        cam8 = "127,127,127,127,127,127,127,127,126,126,126,126,126,126,126,126,125,125,125,125,125,125,125,125,124,124,124,124,124,124,124,124,123,123,123,123,123,123,123,123,122,122,122,122,122,122,122,122,121,121,121,121,121,121,121,121,120,120,120,120,120,120,120,120,115,115,115,115,115,115,115,115,114,114,114,114,114,114,114,114,113,113,113,113,113,113,113,113,112,112,112,112,112,112,112,112,111,111,111,111,111,111,111,111,110,110,110,110,110,110,110,110,108,108,108,108,108,108,108,108,107,107,107,107,107,107,107,107,106,106,106,106,106,106,106,106,105,105,105,105,105,105,105,105,104,104,104,104,104,104,104,104,103,103,103,103,103,103,103,103,102,102,102,102,102,102,102,102,101,101,101,101,101,101,101,101,100,100,100,100,100,100,100,100,99,99,99,99,99,99,99,99,98,98,98,98,98,98,98,98,97,97,97,97,97,97,97,97,96,96,96,96,96,96,96,96,95,95,95,95,95,95,95,95,94,94,94,94,94,94,94,94,93,93,93,93,93,93,93,93,92,92,92,92,92,92,92,92,91,91,91,91,91,91,91,91,90,90,90,90,90,90,90,90,88,88,88,88,88,88,88,88,"
        gpsOff = "False"
        gsmOff = "False"
        brkOff = "False"
        balanceOff = "False"
        changeProgramMemory = "False"
        changeProgramGsm = "False"
        nameProgramFilePgsm = "CID_1222.bin"
        levelOff = "99" 
        levelBuff = "108,107,106,105,104,103,102,101,100,97,96,80,79,78,77,76,75,74,73,72,71,70,69,68,67,66,65,64,63,62,61,60,59,40,36,33,32,31,26"
        levelActivTCP ="94"
        levelActivGPRS = "91,90,89,88,87,86,48,47,46,45,39,35,34,29,27,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5"
        levelDeactivGPRS = "127,126,125,124,123,122,121,120,115,114,113,112,111,110,98,95,93,92,85,84,58,57,56,55,54,53,52,51,50,49,44,43,41,38,37,30,28,25,24,4,3,2,1"
        levelDeactivGPRSRoam = "42"
        changeVoice = "False"
        voiceFile = "voice.bin"

        queryPrefix = "INSERT INTO %s.[_device_transfer] (\"deviceId\", \"gps\", \"gsm\", \"balance\", \"brk\", \"cam1\", \"cam2\", \"cam3\", \"cam4\", \"cam5\", \"cam6\", \"cam7\", \"cam8\", \"gpsOff\", \"gsmOff\", \"brkOff\", \"balanceOff\", \"changeProgramMemory\", \"changeProgramGsm\", \"nameProgramFilePgsm\", \"levelOff\", \"levelBuff\", \"levelActivTCP\", \"levelActivGPRS\", \"levelDeactivGPRS\", \"levelDeactivGPRSRoam\", \"changeVoice\", \"voiceFile\") VALUES " % dbprefix
        querySuffix = "('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')" % (deviceId, gps, gsm, balance, brk, cam1, cam2, cam3, cam4, cam5, cam6, cam7, cam8, gpsOff, gsmOff, brkOff, balanceOff, changeProgramMemory, changeProgramGsm, nameProgramFilePgsm, levelOff, levelBuff, levelActivTCP, levelActivGPRS, levelDeactivGPRS, levelDeactivGPRSRoam, changeVoice, voiceFile)
        query = queryPrefix + querySuffix
        cursor = connection.cursor()
        cursor.execute(query)
        connection.commit()

        return (oid, mac, msid, login, pwd)

class EventTypes:
    MSTypeToContactId = {
        '11':'1103',
        '13':'1103',
        '20':'1101',
        '22':'1101',
        '28':'3011',
        '27':'3013',
        '85':'1101',
    }
    BolidTypeToContactId = {
        'ВЗЛОМКОРПУСА':'3831',
        'ВОССТ.КОРПУСА':'3833',
        'ПОЖ':'1101',
        'ПОЖАР':'1101',
        'СБРОСТРЕВОГИШС':'1103',
        'ВНИМАНИЕ':'1181',
        'ТРЕВОГА':'1231',
        'ТИХАЯТРЕВОГА':'1221',
        'ТРЕВОГАВХОДА':'1341',
        'ИЗМ.ДАТЫ':'6253',
        'ВЗЯТШС':'4003',
        'СНЯТШС':'4011',
        'ПРОГРАММИРОВАНИЕ':'6271',
        'СБРОСПРИБОРА':'3051',
        'ОБР':'3311',
        'КЗ':'3321',
        'АВПИТ':'3011',
        'ВОССТ':'3013',
        'ОБРЫВШС':'3311',
        'РАЗДЕЛВЗЯТ':'4023',
        'РАЗДЕЛСНЯТ':'4021',
        'КОРОТКОЕЗАМЫКАН':'3321',
        'ИДЕНТИФИКАЦИЯХО':'4221',
        'КЗВЫХОДА':'3201',
        'ОБРЫВВЫХОДА':'3201',
        'НЕИСПР.ТЕЛ.ЛИНИИ':'3501',
        'НЕИЗ.СОБЫТИЕ091':'3503',
        'ВЫКЛ.ПРИНТЕРА':'3361',
        'ВКЛ.ПРИНТЕРА':'3363',
        'ИЗМ.ВРЕМЕНИ':'6253',
    }
class MSEvent:
    def __init__(self):
        #event internal identificator
        self.eventId = 0
        #when event has been received
        self.eventReceiveTime = datetime.datetime.fromtimestamp(time.time())
        #when event has been generated
        self.eventGenerateTime = self.eventReceiveTime
        #when event has been saved by ms side
        self.eventMSSaveTime = self.eventReceiveTime
        #when event has been done
        self.eventMySaveTime = self.eventReceiveTime

#PGSM FULL
        self.hasPGSM = False
        self.donePGSM = False
        self.msEventType = 0
        self.msObjectId = 0

#CONTACTID EVENT DATA
        #does event have static data
        self.hasContactId = False
        self.doneContactId = False
        #event type in ContactId's notation
        self.contactIdType = 0
        self.contactIdFull = 0
        self.contactIdZona = 0
        self.contactIdGroup = 0
        self.pcnObjectId = 0

#BOLID DATA
        self.hasBolid = False
        self.doneBolid = False
        self.bolidFull = 0

#MOBILE EVENT DATA
        #does event have gps coordinates
        self.hasMobile = False
        self.doneMobile = False
        #TBD describe coordinate notation and speed measurements
        self.GPSLatitude = 0.0
        self.GPSLongitude = 0.0
        self.GPSSpeed = 0.0
        self.GPSCourse = 0

    def __cmp__(self, y):
        if y == None:
            return 1
        if self.eventId > y.eventId:
            return 1
        if self.eventId < y.eventId:
            return -1
        return 0

    def toString(self):
        ret = 'id(' + str(self.eventId) + ') '
        ret += '(' + str(self.eventGenerateTime) + ',' + str(self.eventReceiveTime) + ') '
        if self.hasPGSM: ret += 'pgsm(' + str(self.msEventType) + ',' + str(self.msObjectId) + ') '
        if self.hasContactId: ret += 'cid(' + str(self.pcnObjectId) + ',' + str(self.contactIdFull) + ') '
        if self.hasBolid: ret += 'bolid(' + str(self.bolidFull) + ') '
        if self.hasMobile: ret += 'gps(' + str(self.GPSLatitude) + ',' + str(self.GPSLongitude) + ',' + str(self.GPSSpeed) + ') '
        return ret

    def merge(self, y):
        if self.eventId != y.eventId: raise RuntimeError('MSEvent: event ids differ: %d,%d' % (int(self.eventId), int(y.eventId)))
        if y.hasPGSM:
            #if self.hasPGSM: raise RuntimeError('MSEvent: event(%d) has pgsm data while received an update' % (int(self.eventId)))
            self.msEventType = y.msEventType
            self.msObjectId = y.msObjectId

        if y.hasContactId:
            #if self.hasContactId: raise RuntimeError('MSEvent: event(%d) has contactid data while received an update' % (int(self.eventId)))
            self.contactIdType = y.contactIdType
            self.contactIdFull = y.contactIdFull
            self.contactIdZona = y.contactIdZona
            self.contactIdGroup = y.contactIdGroup
            self.pcnObjectId = y.pcnObjectId

        if y.hasBolid:
            #if self.hasBolid: raise RuntimeError('MSEvent: event(%d) has bolid data while received an update' % (int(self.eventId)))
            self.bolidFull = y.bolidFull

        if y.hasMobile:
            #if self.hasMobile: raise RuntimeError('MSEvent: event(%d) has mobile data while received an update' % (int(self.eventId)))
            self.GPSLatitude = y.GPSLatitude
            self.GPSLongitude = y.GPSLongitude
            self.GPSSpeed = y.GPSSpeed
            self.GPSCourse = y.GPSCourse

    def PGSMFromMSAPI(self, raw):
        self.hasPGSM = True

        #common parser of xml event
        s = raw.split('/>')[0]
        s = s.split('<')[1]
        s = s.split(' ')
        e = {}
        for prop in s[1:len(s)]:
            prop = prop.split('=')
            e[prop[0]] = prop[1].replace('"', '')

        self.eventType = e['type']
        self.eventGenerateTime = datetime.datetime.strptime(e['datetime'], MSDatetime.FormatMagicAPI)
        self.eventId = int(e['id'])

        self.contactIdType = EventTypes.MSTypeToContactId.get(str(e['type']), 0)
        if self.contactIdType == 0: return

        self.hasContactId = True
        self.contactIdZona  = '099'
        self.contactIdGroup = '97'

        contactId_EType = self.contactIdType[3]
        contactId_Type = self.contactIdType[0:3]
        contactId_Zona = '099'
        contactId_Group = '00'
        contactId_Object = str(self.pcnObjectId)
        if len(contactId_Object) == 3: contactId_Object = '0%s' % (str(contactId_Object))
        if len(contactId_Object) == 2: contactId_Object = '00%s' % (str(contactId_Object))
        if len(contactId_Object) == 1: contactId_Object = '000%s' % (str(contactId_Object))
        self.contactIdFull = '18' + contactId_Object + contactId_EType + contactId_Type + contactId_Group + contactId_Zona + '0'

    def ContactIdFromMSAPI(self, raw):
        #common parser of xml event
        s = raw.split('/>')[0]
        s = s.split('<')[1]
        s = s.split(' ')
        e = {}
        for prop in s[1:len(s)]:
            prop = prop.split('=')
            e[prop[0]] = prop[1].replace('"', '')

        self.hasContactId = True
        cid = e['value']
        contactId_Object = cid[2:6]
        contactId_EType = cid[6:7]
        contactId_Type = cid[7:10]
        contactId_Group = cid[10:12]
        contactId_Zona = cid[12:15]
        self.contactIdFull = cid
        self.contactIdType = "%s%s" % (contactId_Type, contactId_EType)
        self.contactIdGroup = str(contactId_Group)
        self.contactIdZona = str(contactId_Zona)
        self.objectId = int(contactId_Object)
        self.eventGenerateTime = datetime.datetime.strptime(e['date'], MSDatetime.FormatMagicAPI)
        self.eventId = int(e['eventid'])

    def GPSPointFromMSAPI(self, raw):
        self.hasMobile = True
        #common parser of xml event
        s = raw.split('/>')[0]
        s = s.split('<')[1]
        s = s.split(' ')
        e = {}
        for prop in s[1:len(s)]:
            prop = prop.split('=')
            e[prop[0]] = prop[1].replace('"', '')

        self.eventId = int(e['eventid'])
        #LAT
        ddd = float(e['lat'])
        self.GPSLatitudeOrig = ddd
        dd = int(ddd)
        mm = int((ddd - dd)*60)
        ss = str(((ddd - dd)*60 - mm)*60)
        mm = str(mm)
        if (len(mm)) < 2:
            mm = '0%s' % (mm[0])
        dd = str(dd)
        self.GPSLatitude = dd[0:2] + mm[0:2] + '.' + ss.replace('.','')[0:5]
        #LON
        ddd = float(e['lon'])
        self.GPSLongitudeOrig = ddd
        dd = int(ddd)
        mm = int((ddd - dd)*60)
        ss = str(((ddd - dd)*60 - mm)*60)
        mm = str(mm)
        if (len(mm)) < 2:
            mm = '0%s' % (mm[0])
        dd = str(dd)
        self.GPSLongitude = dd[0:2] + mm[0:2] + '.' + ss.replace('.','')[0:5]
        self.GPSSpeed = e['speed']
        self.eventGenerateTime = datetime.datetime.strptime(e['date'], MSDatetime.FormatMagicAPI)
        if e.has_key('course'): self.GPSCourse = e['course']
        #self.eventId = e['eventInternalId']

    def PGSMFromMSDB(self, raw):
        self.hasPGSM = True

        self.contactIdZona  = '099'
        self.contactIdGroup = '99'
        self.eventId = int(raw[0])
        self.eventType = int(raw[3])
        self.eventGenerateTime = raw[2]
        self.eventSaveTime = raw[4]
        self.contactIdType = EventTypes.MSTypeToContactId.get(str(self.eventType), 0)
        if self.contactIdType == 0:
            return
        self.hasContactId = True

    def PGSMFromMSDB_AUTOTEST(self, raw):
        self.hasPGSM = True
        self.eventId = int(raw[0])
        self.eventType = int(raw[3])
        self.eventGenerateTime = raw[2]
        self.contactIdType = EventTypes.MSTypeToContactId.get(str(self.eventType), 0)
        if self.contactIdType == 0:
            return
        self.hasContactId = True

    def BolidFromMSDB(self, raw):
        self.eventId = int(raw[0])
        self.eventGenerateTime = raw[2]
        rawParsed   = str(raw[3]).split('|')
        rawType     = rawParsed[2].replace(' ', '').decode('cp866').encode('u8')
        rawZone     = rawParsed[3].replace(' ', '')
        rawZone     = rawZone.split('/')
        if len(rawZone) == 2: self.contactIdZona = str(rawZone[1])
        rawGroup    = rawParsed[4].replace(' ', '')
        if len(rawGroup) > 0: self.contactIdGroup = str(rawGroup)

        self.contactIdType = EventTypes.BolidTypeToContactId.get(str(rawType), 0)
        if self.contactIdType == 0:
            return
        self.hasBolid = False
        self.hasContactId = True

    def ContactIdFromMSDB(self, raw):
        self.eventId = int(raw[0])
        self.eventGenerateTime = raw[2]
        cid = ''
        for i in raw[3]:
            t = str(hex(ord(i)))
            if len(t) == 3:
                t = '0%s' % (t[2])
            else:
                t = '%s%s' % (t[2], t[3])
            cid += t
        contactId_Object = cid[2:6]
        contactId_EType = cid[6:7]
        contactId_Type = cid[7:10]
        contactId_Group = cid[10:12]
        contactId_Zona = cid[12:15]
        self.contactIdFull = cid
        self.contactIdType = "%s%s" % (contactId_Type, contactId_EType)
        self.contactIdGroup = str(contactId_Group)
        self.contactIdZona = str(contactId_Zona)
        self.objectId = int(contactId_Object)
        self.hasContactId = True

class MSRuntimeDatabase:
    def __init__(self):
        try:
            self.locker = multiprocessing.Lock()
        except:
            logDatabase.warning('MSRuntimeDatabase: multiprocessing is deprecated - slowing down and using threading instead')
            self.locker = threading.Lock()
        self.minutes = {}

    def genAllUndoneEvents(self):
        minutes = None
        self.locker.acquire()
        try:
            minutes = self.minutes.keys()
        finally:
            self.locker.release()

        minutes.sort()
        for m in minutes:
            events = None
            ids = None
            self.locker.acquire()
            try:
                if self.minutes.has_key(m):
                    events = self.minutes[m]
                    ids = events.keys()
                else:
                    continue
            finally:
                self.locker.release()
            ids = set(ids)
            for eid in ids:
                if events.has_key(eid):
                    e = events[eid]
                    if (e.hasPGSM and not e.donePGSM) or (e.hasContactId and not e.doneContactId) or (e.hasMobile and not e.doneMobile): yield e

    def append(self, events):
        localminutes = {}
        now = datetime.datetime.today()
        for e in events:
            estamp = e.eventGenerateTime
            key = datetime.datetime(year=estamp.year,
                                    month=estamp.month,
                                    day=estamp.day,
                                    hour=estamp.hour,
                                    minute=estamp.minute)
            if now - key > EVENT_WINDOW:
                logDatabase.debug('MSRuntimeDatabase::append: ignore: ' + str(e.toString()))
                continue
            if not localminutes.has_key(key): localminutes[key] = {}
            localminutes[key][e.eventId] = e

        self.locker.acquire()
        try:
            appended=0
            merged=0
            for m,mevents in localminutes.iteritems():
                dbevents = self.minutes.get(m, None)
                if not dbevents:
                    dbevents = {}
                    self.minutes[m] = dbevents
                for eid,e in mevents.iteritems():
                    dbe = dbevents.get(e.eventId, None)
                    if dbe == None:
                        dbevents[e.eventId] = e
                        appended += 1
                    else:
                        try:
                            dbe.merge(e)
                            merged += 1
                        except Exception, err:
                            logDatabase.error('MSRuntimeDatabase::append: merge failed: ' + str(err))
                            continue
            logDatabase.info('MSRuntimeDatabase::append: %d(%d,%d)' % (len(events), appended, merged))
        finally:
            self.locker.release()

    def cleanup(self):
        self.locker.acquire()
        keys = None
        try:
            keys = self.minutes.keys()
        finally:
            self.locker.release()
        keys.sort()
        now = datetime.datetime.today()
        for k in keys:
            if now - k < EVENT_WINDOW: continue
            events = None
            self.locker.acquire()
            try:
                if self.minutes.has_key(k): events = self.minutes.pop(k)
            finally:
                self.locker.release()
            #TODO: analyze events

class MSAPIReader:
    def __init__(self, MSAPIUrl='http://api.car-online.ru'):
        self.MSUrl = MSAPIUrl

    def readURLData(self, Request, SKey, TimeRange, Pattern=None):
        url = self.MSUrl + '/do?data=%s&skey=%s&begin=%s&end=%s' % (Request,
                                                                             SKey,
                                                                             TimeRange[0],
                                                                             TimeRange[1])
        if Pattern != None: url += '&pattern=' + str(Pattern)
        f = urllib.urlopen(url, None, 2)
        d = f.readlines()
        if len(d) == 0:
            return None
        return (d, url)

    def getEventsPGSM(self, objects):
        lowerbound  = datetime.datetime.today() - EVENT_WINDOW
        lowerbound  = lowerbound.strftime(MSDatetime.FormatMagicAPI)

        upperbound  = datetime.datetime.today() + EVENT_WINDOW
        upperbound  = upperbound.strftime(MSDatetime.FormatMagicAPI)

        events      = []
        for k,o in objects.iteritems():
            res = self.readURLData('events', o.objectSKey, (lowerbound, upperbound))
            if res == None: continue
            logReader.debug('MSAPIReader::getEventsPGSM: obj(%s,%s) eventnum(%d) query: %s', str(o.objectMSId), str(o.objectPCNId), len(res[0]), res[1])
            for e in res[0]:
                try:
                    realEvent = MSEvent()
                    realEvent.PGSMFromMSAPI(e)
                    realEvent.msObjectId = o.objectMSId
                    realEvent.pcnObjectId = o.objectPCNId
                    events.append(realEvent)
                except Exception, err:
                    logReader.warning('MSAPIReader::getEventsPGSM: unparsed: %s', str(e))
                    logReader.warning('MSAPIReader::getEventsPGSM: error: %s', str(err))
                    continue
        return events

    def getEventsContactId(self, objects):
        lowerbound  = datetime.datetime.today() - EVENT_WINDOW
        lowerbound  = lowerbound.strftime(MSDatetime.FormatMagicAPI)

        upperbound  = datetime.datetime.today() + EVENT_WINDOW
        upperbound  = upperbound.strftime(MSDatetime.FormatMagicAPI)

        events      = []
        for k,o in objects.iteritems():
            res = self.readURLData('contactId', o.objectSKey, (lowerbound, upperbound))
            if res == None: continue
            logReader.debug('MSAPIReader::getEventsContactId: obj(%s:%s) eventnum(%d) query: %s', str(o.objectMSId), str(o.objectPCNId), len(res[0]), res[1])
            for e in res[0]:
                try:
                    realEvent = MSEvent()
                    realEvent.ContactIdFromMSAPI(e)
                    realEvent.msObjectId = o.objectMSId
                    realEvent.pcnObjectId = o.objectPCNId
                    events.append(realEvent)
                except Exception, err:
                    logReader.warning('MSAPIReader::getEventsContactId: unparsed: %s', str(e))
                    logReader.warning('MSAPIReader::getEventsContactId: error: %s', str(err))
                    continue
        return events

    def getEventsGPS(self, objects):
        lowerbound  = datetime.datetime.today() - EVENT_WINDOW
        lowerbound  = lowerbound.strftime(MSDatetime.FormatMagicAPI)

        upperbound  = datetime.datetime.today() + EVENT_WINDOW
        upperbound  = upperbound.strftime(MSDatetime.FormatMagicAPI)

        events      = []
        for k,o in objects.iteritems():
            res = self.readURLData('gpsPoints', o.objectSKey, (lowerbound, upperbound), 249)
            if res == None: continue
            logReader.debug('MSAPIReader::getEventsGPS: obj(%s:%s) eventnum(%d) query: %s', str(o.objectMSId), str(o.objectPCNId), len(res[0]), res[1])
            for e in res[0]:
                try:
                    realEvent = MSEvent()
                    realEvent.GPSPointFromMSAPI(e)
                    realEvent.msObjectId = o.objectMSId
                    realEvent.pcnObjectId = o.objectPCNId
                    events.append(realEvent)
                except Exception, err:
                    logReader.warning('MSAPIReader::getEventsGPS: unparsed: %s', str(e))
                    logReader.warning('MSAPIReader::getEventsGPS: error: %s', str(err))
                    continue
        return events

    def getEventsGPS_ForSochi(self, objects):
        lowerbound  = datetime.datetime.today() - EVENT_WINDOW
        lowerbound  = lowerbound.strftime(MSDatetime.FormatMagicAPI)

        upperbound  = datetime.datetime.today() + EVENT_WINDOW
        upperbound  = upperbound.strftime(MSDatetime.FormatMagicAPI)

        events      = []
        for k,o in objects.iteritems():
            res = self.readURLData('gpsPoints', o.objectSKey, (lowerbound, upperbound), 249)
            if res == None:
                logReader.debug('MSAPIReader::getEventsGPS: obj(%s:%s:%s): no events', str(o.objectMSId), str(o.objectPCNId), str(o.objectSKey))
                continue
            logReader.debug('MSAPIReader::getEventsGPS: obj(%s:%s) eventnum(%d) query: %s', str(o.objectMSId), str(o.objectPCNId), len(res[0]), res[1])
            for e in res[0]:
                try:
                    realEvent = MSEvent()
                    realEvent.GPSPointFromMSAPI(e)
                    realEvent.msObjectId = o.objectMSId
                    realEvent.pcnObjectId = o.objectPCNId
                    events.append(realEvent)
                    break
                except Exception, err:
                    logReader.warning('MSAPIReader::getEventsGPS: unparsed: %s', str(e))
                    logReader.warning('MSAPIReader::getEventsGPS: error: %s', str(err))
                    continue
        return events

    def getEventsSOS_ForSochi(self, objects):
        lowerbound  = datetime.datetime.today() - EVENT_WINDOW
        lowerbound  = lowerbound.strftime(MSDatetime.FormatMagicAPI)

        upperbound  = datetime.datetime.today() + EVENT_WINDOW
        upperbound  = upperbound.strftime(MSDatetime.FormatMagicAPI)

        events      = []
        for k,o in objects.iteritems():
            res = self.readURLData('events', o.objectSKey, (lowerbound, upperbound))
            if res == None:
                logReader.debug('MSAPIReader::getEventsSOS: obj(%s:%s:%s): no events', str(o.objectMSId), str(o.objectPCNId), str(o.objectSKey))
                continue
            logReader.debug('MSAPIReader::getEventsSOS: obj(%s:%s) eventnum(%d) query: %s', str(o.objectMSId), str(o.objectPCNId), len(res[0]), res[1])
            for e in res[0]:
                try:
                    realEvent = MSEvent()
                    realEvent.PGSMFromMSAPI(e)
                    if realEvent.eventType != '85': continue
                    realEvent.msObjectId = o.objectMSId
                    realEvent.pcnObjectId = o.objectPCNId
                    realEvent.objectSKey = o.objectSKey
                    events.append(realEvent)
                    break
                except Exception, err:
                    logReader.warning('MSAPIReader::getEventsSOS: unparsed: %s', str(e))
                    logReader.warning('MSAPIReader::getEventsSOS: error: %s', str(err))
                    continue

        for realEvent in events:
            res = self.readURLData('gpsPoints', realEvent.objectSKey, (lowerbound, upperbound))
            if res == None:
                logReader.debug('MSAPIReader::getEventsSOS:2: obj(%s:%s:%s): no events', 'N/A', str(realEvent.objectPCNId), str(realEvent.objectSKey))
                continue
            logReader.debug('MSAPIReader::getEventsSOS:2: obj(%s:%s) eventnum(%d) query: %s', 'N/A', str(realEvent.objectPCNId), len(res[0]), res[1])
            for e in res[0]:
                try:
                    realEvent.GPSPointFromMSAPI(e)
                    break
                except Exception, err:
                    logReader.warning('MSAPIReader::getEventsSOS:2: unparsed: %s', str(e))
                    logReader.warning('MSAPIReader::getEventsSOS:2: error: %s', str(err))
                    continue
        return events

class MSDBReader:
    def __init__(self, Host, Port, DatabaseName, User, Password):
        self.DBHost = Host
        self.DBPort = Port
        self.DBName = DatabaseName
        self.DBUser = User
        self.DBPass = Password
        self.connection = None

    def getConnectionName(self):
        return '(MSSQL:%s:%s:%s)' % (str(self.DBHost), str(self.DBPort), str(self.DBName))
    def getConnection(self):
        if MSDBDriver == "pymssql":
            return pymssql.connect(host=self.DBHost + ':' + self.DBPort,
                                database=self.DBName, user=self.DBUser, password=self.DBPass)
        if MSDBDriver == "pyodbc":
            return pyodbc.connect(driver='{SQL Server}', server=self.DBHost, port=self.DBPort, database=self.DBName, uid=self.DBUser, pwd=self.DBPass)
        raise RuntimeError('can\'t connect: no any appropriate db driver loaded')
    def isConnectionGood(self):
        return False
    def connect(self):
        if self.isConnectionGood():
            return True
        attemptCount = 0
        while True:
            try:
                self.connection = self.getConnection()
                break
            except Exception, err:
                secs = 2
                attemptCount += 1
                if attemptCount > 3:
                    attemptCount = 0
                    secs *= 10
                log.warning('MSDBReader::connect: Connection to %s failed: attempt(%s) secs to sleep(%s) error: %s' % (str(self.getConnectionName()), str(attemptCount), str(secs), str(err)))
                time.sleep(secs)
                continue
        return True

    def getEventsPGSM(self, objects):
        self.connect()
        lowerbound  = datetime.datetime.today() - EVENT_WINDOW
        lowerbound  = lowerbound.strftime(MSDatetime.FormatMagicDB)
        cursor      = self.connection.cursor()

        query       = "SELECT id,deviceId,eventTime,type,SaveTime FROM _event_data_internal WHERE eventTime>'%s'" % (str(lowerbound))
        logReader.debug('MSDBReader::getEventsPGSM: %s', query)
        cursor.execute(query)
        eventsRaw   = cursor.fetchall()
        events      = []
        for e in eventsRaw:
            try:
                o = objects.get(e[1], None)
                if o == None: continue
                realEvent = MSEvent()
                realEvent.PGSMFromMSDB(e)
                realEvent.msObjectId = o.objectMSId
                realEvent.pcnObjectId = o.objectPCNId
                events.append(realEvent)
            except Exception, err:
                #pdb.set_trace()
                logReader.warning('MSDBReader::getEventsPGSM: unparsed: %s' % (str(e)))
                logReader.warning('MSDBReader::getEventsPGSM: error: %s' % (str(err)))
                continue
        return events

    def getEventsContactId(self, objects):
        self.connect()
        lowerbound  = datetime.datetime.today() - EVENT_WINDOW
        lowerbound  = lowerbound.strftime(MSDatetime.FormatMagicDB)
        cursor      = self.connection.cursor()

        events      = []
        query       = "SELECT eventId,deviceId,eventTime,contactData FROM _contactId_data_internal WHERE eventTime>'%s'" % (str(lowerbound))
        cursor.execute(query)
        logReader.debug('MSDBReader::getEventsContactId: %s', query)
        eventsRaw   = cursor.fetchall()
        for e in eventsRaw:
            try:
                o = objects.get(e[1], None)
                if o == None: continue
                realEvent = MSEvent()
                realEvent.ContactIdFromMSDB(e)
                realEvent.msObjectId = o.objectMSId
                realEvent.pcnObjectId = o.objectPCNId
                events.append(realEvent)
            except Exception, err:
                logReader.warning('MSDBReader::getEventsContactId: unparsed: %s' % (str(e)))
                logReader.warning('MSDBReader::getEventsContactId: error: %s' % (str(err)))
                continue
        return events

    def getEventsBolid(self, objects):
        self.connect()
        lowerbound  = datetime.datetime.today() - EVENT_WINDOW
        lowerbound  = lowerbound.strftime(MSDatetime.FormatMagicDB)
        cursor      = self.connection.cursor()

        events      = []
        query       = "SELECT eventId,deviceId,eventTime,rs232str FROM _rs232_data WHERE eventTime>'%s'" % (str(lowerbound))
        logReader.debug('MSDBReader::getEventsBolid: %s', query)
        cursor.execute(query)
        eventsRaw   = cursor.fetchall()
        for e in eventsRaw:
            try:
                o = objects.get(e[1], None)
                if o == None: continue
                realEvent = MSEvent()
                realEvent.BolidFromMSDB(e)
                realEvent.msObjectId = o.objectMSId
                realEvent.pcnObjectId = o.objectPCNId
                events.append(realEvent)
            except Exception, err:
                logReader.warning('MSDBReader::getEventsBolid: unparsed: %s' % (str(e)))
                logReader.warning('MSDBReader::getEventsBolid: error: %s' % (str(err)))
                continue
        return events


class MSSochiWriter:
    def __init__(self, Path, User, Pass):
        self.DBPath = Path
        self.DBUser = User
        self.DBPass = Pass

    def writeEventsMobile(self, events):
        eventsWritten = 0
        for e in events:
            try:
                e.eventGenerateTime = e.eventGenerateTime - datetime.timedelta(hours=4)
                analogIn = ''
                if str(e.eventType) == '85':
                    analogIn = '<AnalogIn num=1 val=1>'
                body = """
                <?xml version="1.0" encoding="windows-1251"?>
                <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
                <soapenv:Header/>
                <soapenv:Body>
                <ws:PutCoord>
                <ObjectID>%s</ObjectID>
                <Coord time="%s" lon="%s" lat="%s" speed="%s" dir="%s" valid="1"/>
                %s
                </ws:PutCoord>
                </soapenv:Body>
                </soapenv:Envelope>""" % (str(e.objectId),
                                          str(e.eventGenerateTime.strftime('%Y-%m-%dT%H:%M:%SZ')),
                                          str(e.GPSLongitudeOrig),
                                          str(e.GPSLatitudeOrig),
                                          str(e.GPSSpeed),
                                          str(e.GPSCourse),
                                          analogIn)

                req = urllib2.Request(self.DBPath)
                auth = 'Basic ' + base64.urlsafe_b64encode('%s:%s' % (self.DBUser, self.DBPass))
                req.add_header('Authorization', auth)
                req.add_header('SOAPAction', '""')
                req.add_header('Content-Length', str(len(body)))
                req.add_data(body)
                conn = urllib2.urlopen(req)
                res = conn.read()
                if res.find(str(e.objectId)) == -1:
                    logWriter.warning('writeEventsStatic: object(%s) no acknowledge' % str(e.objectId))
                else:
                    if e.hasPGSM: e.donePGSM = True
                    if e.hasContactId: e.doneContactId = True
                    if e.hasMobile: e.doneMobile = True

                eventsWritten +=1
                logWriter.info('writeEventsStatic: object(%s) written %s' % (str(e.objectId), str(e.eventGenerateTime.strftime('%Y-%m-%dT%H:%M:%SZ'))))

            except Exception, err:
                logWriter.error('writeEventsStatic: object(%s) can\'t write event: %s' % (str(e.objectId), str(err)))
                continue
        return eventsWritten


class PCNFBWriter:
    @staticmethod
    def writeEventRaw(dbCursor, event, recId):
        recordId        = str(recId)
        recordIdMes     = str(event.contactIdType)
        recordIdObj     = str(event.pcnObjectId)
        recordSDate     = str(event.eventGenerateTime.strftime(MSDatetime.FormatPCNDB))
        recordXUser     = 'magicsys'
        recordPhone     = str(event.eventSaveTime.strftime(MSDatetime.FormatPCNDB))
        recordIDPr      = '2'
        recordIDPPL     = '-1'
        recordIDZon     = '-1'
        recordZona      = str(event.contactIdZona)
        recordRazdel    = str(event.contactIdGroup)
        recordSTR1      = str(event.contactIdFull)
        recordSTR2      = '5011 180994R65500000'
        recordCOMN      = 'GPRS'

        query = "INSERT INTO SGN (ID, ID_MES, ID_OBJ, SDATE, XUSER, PHONE, ID_PR, ID_PPL, ID_ZON, ZONA, RAZDEL, STR1, STR2, COMN) VALUES" + \
                                 "('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')" % \
                                 (recordId, recordIdMes, recordIdObj, recordSDate, recordXUser, recordPhone, recordIDPr, recordIDPPL, \
                                  recordIDZon, recordZona, recordRazdel, recordSTR1, recordSTR2, recordCOMN)
        try:
            dbCursor.execute(query)
            if event.hasContactId: event.doneContactId = True
            if event.hasBolid: event.doneBolid = True
            if event.hasPGSM: event.donePGSM = True
            logWriter.info('done %s: %s' % (str(recId), query))
            recId += 1
        except (Exception), err:
            logWriter.error('failed %s: %s' % (str(recId), query))
            logWriter.error('reason: %s' % str(err))
        finally:
            return recId

    @staticmethod
    def writeEvent(dbCursor, event, recId):
        if not event.hasPGSM and not event.hasContactId and not event.hasBolid:
            logWriter.error('writeEvent: event has no static data')
            return recId
        if event.contactIdType == None:
            logWriter.error('writeEvent: event has no contact id type')
            return recId
        if (datetime.datetime.today() - event.eventGenerateTime) >= EVENT_WINDOW:
            logWriter.error('writeEvent: cant write message older than 30 mins: ' + event.toString())
            return recId
        return PCNFBWriter.writeEventRaw(dbCursor, event, recId)

    @staticmethod
    def getLastEventId(dbCursor):
        query = "SELECT MAX(ID) FROM SGN"
        try:
            dbCursor.execute(query)
            buff    = dbCursor.fetchall()
            recId   = int(buff[0][0])
            return recId + 1
        except Exception, err:
            logWriter.info('getLastEventId: can\'t get last rec id - reset to 1, code: %s' % (str(err)))
            return 1

    def __init__(self, Path, User, Pass):
        self.DBPath = Path
        self.DBUser = User
        self.DBPass = Pass
        self.connection = None

    def getConnection(self):
        return kinterbasdb.connect(dsn=self.DBPath, user=self.DBUser, password=self.DBPass)
    def getConnectionName(self):
        return '(FB:%s)' % (str(self.DBPath))
    def isConnectionGood(self):
        return False

    def connect(self):
        if self.isConnectionGood():
            return True
        attemptCount = 0
        self.connection = self.getConnection()
        return self.connection
        while True:
            try:
                self.connection = self.getConnection()
                break
            except Exception, err:
                secs = 2
                attemptCount += 1
                if attemptCount > 3:
                    attemptCount = 0
                    secs *= 10
                log.warning('Worker::connect: Connection to %s failed: attempt(%s) secs to sleep(%s) error: %s' % (str(self.getConnectionName()), str(attemptCount), str(secs), str(err)))
                time.sleep(secs)
                continue
        return True

    def writeEventsStatic(self, events):
        self.connect()
        cursor  = self.connection.cursor()
        recId   = PCNFBWriter.getLastEventId(cursor)
        eventsWritten = 0
        for e in events:
            try:
                e.userWriterId = str(self.DBUser)
                recId = PCNFBWriter.writeEvent(None, e, recId)
                #recId = PCNFBWriter.writeEvent(cursor, e, recId)
                eventsWritten +=1

            except Exception, err:
                logWriter.error('writeEventsStatic: object(%s) can\'t write event: %s' % (str(e.objectId), str(err)))
                continue
        self.connection.commit()
        return eventsWritten


